/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-03
 * V4.0
 */
package com.jphenix.servlet.filter;

//#region 【引用区】
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.jphenix.driver.json.Json;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.interfaceclass.IBeanFactory;
import com.jphenix.share.lang.SInteger;
import com.jphenix.share.lang.SString;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.tools.HttpCall;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.exceptions.MsgException;
import com.jphenix.standard.servlet.IBytesFilter;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequestManager;
import com.jphenix.standard.servlet.IResponseManager;
import com.jphenix.standard.servlet.api.IFilterConfig;
import com.jphenix.standard.servlet.api.IRequest;
import com.jphenix.standard.servlet.api.IResponse;
//#endregion

//#region 【说明区】
/**
 * 国际化处理过滤器，主要处理静态页面中的国际化信息
 * 国际化信息格式： {*[CHS:简体中文信息][CHT:繁体中文信息][EN:英文信息]*}
 * 
 * 参数信息过滤器 过滤 <%=key%>  
 * 
 * 在配置文件中如果配置了 <filter_parse_html_file>1</filter_parse_html_file> 程序会读取html文件，解析其中的宏标签。
 * 通常配置值为0，这样可以将html文件放到nginx或者apache中托管。而程序会读取扩展名为htm的文件，解析其中的红标签并输出。
 * 
 * 2019-04-16 完善了请求和反馈编码处理
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2019-08-20 读取部分不能采用缓存方式处理
 *            增加了常用方法
 * 2019-09-24 新增处理文件内容方法（包含框架包内部文件）
 * 2020-06-30 在获取新的HttpServletRequestImpl类实例时，将fe类实例传入其中
 * 2020-08-06 禁止读取jar内部文件，改用独立的过滤器来处理
 * 2022-09-04 隔离了ServletApi，兼容新老Tomcat
 *            去掉了无用注释
 * 2022-09-05 修改了发现的错误
 * 2022-09-07 增加了宏标签，判断会话中如果不存在指定值，则终止输出内容或者重定向url
 * 2024-08-11 新增解析SSI标签，支持 include 标签，并且标签中文件路径支持 ${} 变量，变量在配置文件中 <page_var><xxx></xxx></page_var>进行配置
 * 
 * @author 刘虻
 * 2013-4-6 下午6:50:24
 */
//#endregion
@BeanInfo({"bytefilter"})
@Running({"90"})
@ClassInfo({"2024-08-11 23:57","字节信息处理过滤器"})
public class ByteFilter extends ABase implements IFilter,IBytesFilter {

	//#region 【声明区】
    private   String             filterActionExtName = null; // 需要过滤的扩展名
    protected BaseFilter         bf                  = null; // 过滤器管理类
    protected IFilterConfig      config              = null; // 过滤器配置信息处理类
    private   Map<String,String> parameterMap        = null; // 参数信息对照容器
    protected String             encoding            = null; // 编码
    //#endregion
    
    //#region 类 RedirectException
    /**
     * 重定向异常
     * @author 马宝刚
     * 2015年4月27日
     */
    protected class RedirectException extends Exception {
        /**
         * 穿行标识
         */
        private static final long serialVersionUID = 633343585753948325L;

        /**
         * 构造函数
         * @author 马宝刚
         */
        public RedirectException() {
            super();
        }
    }
    //#endregion
    
    //#region 构造函数
    /**
     * 构造函数
     * @author MBG
     */
    public ByteFilter(IBeanFactory bf) {
    	super();
    	setBeanFactory(bf); //设置类加载器
    }
    //#endregion
    
    //#region getIndex() 获取排序索引，数值越小越先执行
    /**
     * 获取排序索引，数值越小越先执行
     * 刘虻
     * 2013-4-6 下午6:50:24
     */
    @Override
    public int getIndex() {
        return 10; //优先级比较高 最高为0
    }
    //#endregion
    
    //#region getFilterConfig() 获取过滤器配置信息类
    /**
     * 获取过滤器配置信息类
     * 刘虻
     * 2013-4-7 上午11:27:59
     * @return 过滤器配置信息类
     */
    protected IFilterConfig getFilterConfig() {
        return config;
    }
    //#endregion
    
    //#region doFilter(req,resp) 执行过滤
    /**
     * 覆盖方法
     * 刘虻
     * 2013-4-6 下午6:50:25
     */
    @Override
    public boolean doFilter(IRequestManager req, IResponseManager resp) throws Exception {
        //动作路径
        String uri  = req.getServletPath();
        //文件路径
        String filename = SFilesUtil.getAllFilePath(uri,req.getWebBasePath());
        File file = new File(filename); //构建要解析的文件
        if (file.exists()) {
            if (!file.isDirectory()) {
                try {
                    serveFile(req, resp, file);
                }catch(RedirectException e) {
                    //已经执行了重定向，无需再往后处理
                    return true;
                }
            } else {
                log("Filter Error->Not found the file:["+filename+"]");
                return false;
            }
        }
        
        /*
         * 标记该动作已经被处理完
         * 因为操作过读入流，该过滤器读取完之后，
         * 就不能被其他过滤器读取了。
         */
        return true;
    }
    //#endregion
    
    //#region canonicalizePath(path) 处理目标文件相对路径
    /**
     * 处理目标文件相对路径
     * 刘虻
     * 2012-7-27 上午9:01:40
     * @param path 页面请求中的文件路径信息
     * @return 处理后的文件相对路径
     */
    protected String canonicalizePath(String path) {
        if (path == null || path.length() == 0) {
            return path;
        }
        List<String> pathElems = new ArrayList<String>(6);
        char[] pa = path.toCharArray();
        int n = pa.length;
        int s = -1;
        int lev = 0;
        for (int i = 0; i < n; i++) {
            if (s < 0) {
                if (pa[i] != '/' && pa[i] != '\\') {
                    s = i;
                }
            } else {
                boolean f = false;
                if (pa[i] == '?') {
                    f = true;
                }
                if (pa[i] == '/' || pa[i] == '\\' || f) {
                    String el = new String(pa, s, i - s);
                    if ("..".equals(el)) {
                        if (pathElems.size() > 0) {
                            pathElems.remove(pathElems.size() - 1);
                        } else {
                            lev--;
                        }
                        // else exception ?
                    } else if (".".equals(el) == false) {
                        if (lev >= 0) {
                            pathElems.add(el);
                        } else {
                            lev++;
                        }
                    }
                    if (f) {
                        s = i;
                        break;
                    }
                    s = -1;
                }
            }
        }
        if (s > 0) {
            String el = new String(pa, s, n - s);
            if ("..".equals(el)) {
                if (pathElems.size() > 0) {
                    pathElems.remove(pathElems.size() - 1);
                }
                // else exception ?
            } else if (".".equals(el) == false) {
                if (lev >= 0) {
                    pathElems.add(el);
                }
            }
        } else {
            pathElems.add("");
        }
        if (pathElems.size() == 0) {
            return "";
        }
        StringBuffer result = new StringBuffer(n);
        result.append(pathElems.get(0));
        n = pathElems.size();
        for (int i = 1; i < n; i++) {
            result.append('/').append(pathElems.get(i));
        }
        // System.err.println("Before "+path+" after "+result);
        return result.toString();
    }
    //#endregion
    
    //#region 【内部方法】 parseSsi(cnt,os) 解析SSI节点
    /**
     * 解析SSI节点
     * 
     * <!--#include virtual="${sub_path}/square/common/brick_header.htm" -->
     * <!--# include file="html1.html" -->
     * 
     * virtual 相对于网站根路径
     * file    相对于当前请求页面路径
     * 
     * @param bos         输出流
     * @param cnt         节点内容
     * @param req         页面请求对象
     * @param resp        页面反馈对象
     * @param pageParaMap 页面参数容器
     * @throws Exception  异常
     * 2024年8月11日
     * @author MBG
     */
    private void parseSsi(
    		ByteArrayOutputStream bos,
    		String                cnt,
    		IRequestManager       req,
    		IResponseManager      resp,
    		Map<String,String>    pageParaMap
    		) throws Exception {
    	if(cnt==null || cnt.length()<1) {
    		return;
    	}
    	String srcCnt = cnt; // 保留原始内容，用于日志输出
    	cnt = BaseUtil.trim(cnt," ","\t","\r","\n");
    	
    	int point = cnt.indexOf(" ");
    	if(point<0) {
    		point = cnt.indexOf("\t");
        	if(point<0) {
        		point = cnt.indexOf("\r");
            	if(point<0) {
            		point = cnt.indexOf("\n");
                	if(point<0) {
                		warning("The SSI Node Is Error:"+srcCnt);
                		return;
                	}
            	}
        	}
    	}

    	// 节点功能
    	String method = cnt.substring(0,point).toLowerCase();
    	cnt = BaseUtil.trim(cnt.substring(point+1)," ","\t","\r","\n");
    	
    	//#region 处理 include 引用其他文件
    	if(method.equals("include")) {
    		
    		point = cnt.indexOf("=");
    		if(point<0) {
        		warning("The SSI Node Is Error:"+srcCnt);
        		return;
    		}
    		// 路径类型
    		String type = cnt.substring(0,point).toLowerCase();
    		cnt = BaseUtil.trim(cnt.substring(point+1)," ","\t","\r","\n","\'","\"");
    		// 替换掉路径中的变量
    		cnt = parsePageVar(cnt);

    		if(type.equals("virtual")) {
    			// 相对于网站根路径
    			cnt = path(cnt);
    		}else {
    			// 相对于当前请求路径
    			String servletPath = req.getServletPath();
    			point = servletPath.lastIndexOf("/");
    			if(point<0) {
    				servletPath = "";
    			}else {
    				servletPath = servletPath.substring(0,point);
    			}
    			cnt = path(servletPath+"/"+cnt);
    		}
			// 检测目标文件是否存在
			File targetFile = new File(cnt);
			if(!targetFile.exists()) {
				warning("SSI The Target File:["+cnt+"] Not Exists,Node Content:"+srcCnt);
				return;
			}
			// 读取目标文件内容
	        //处理内容
	        fixContent(new FileInputStream(targetFile),bos,req,resp,pageParaMap,null);
    	}
    	//#endregion
    }
    //#endregion
    
    //#region 【内部方法】parsePageVar(content) 替换掉内容中的变量
    /**
     * 替换掉内容中的变量
     * @param content
     * @return
     * 2024年8月11日
     * @author MBG
     */
    private String parsePageVar(String content) {
		// 替换路径中的变量
		Pattern pattern = Pattern.compile("(\\$\\{(.+?)\\})");
		// 替换结果
		StringBuffer sbf = new StringBuffer();
		Matcher matcher = pattern.matcher(content);
		String key;
        while (matcher.find()) {
        	key =  matcher.group();
        	if(key.length()>3) {
        		key = key.substring(2,key.length()-1);
        		matcher.appendReplacement(sbf, p("page_var/"+key));
        	}
        }
        matcher.appendTail(sbf);
        return sbf.toString();
    }
    //#endregion
    
    //#region 【内部方法】 getSSiContent(in) 读取SSI页签内容
    /**
     * 读取SSI页签内容
     * <!--#include xxxxx -->
     * @param in          文件读入流
     * @return            页签内容
     * @throws Exception  异常
     * 2024年8月11日
     * @author MBG
     */
    private String getSSiContent(InputStream in) throws Exception {
        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        int     bStep      = 0;     // 关键字读取步骤
        int     ele        = -1;    // 读取的字节元素
        while((ele=in.read())!=-1) {
            if(bStep==0 && ele=='-') {
            	bStep = 1;
            	continue;
            }else if(bStep==1 && ele=='-') {
            	bStep = 2;
            	continue;
            }else if(bStep==2 && ele=='>') {
            	return reBos.toString();
            }else {
            	if(bStep==1) {
            		reBos.write('-');
            	}else if(bStep==2) {
            		reBos.write('-');
            		reBos.write('-');
            	}
            	bStep = 0;
            	reBos.write(ele);
            }
        }
        return reBos.toString();
    }
    //#endregion
    
    //#region 【内部方法】getScript(in,enc) 读取 <% %> 中的动态脚本内容
    /**
     * 读取 <% %> 中的动态脚本内容
     * 如果开头为 <%#  会忽略期间的动态字符 <% %> 直接找到末尾的 #%>
     * @param in        读入流
     * @param enc     脚本编码格式
     * @return            脚本内容
     * @throws Exception 异常
     * 2014年11月26日
     * @author 马宝刚
     */
    private String getScript(InputStream in,String enc) throws Exception {
        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        int readCount = 0; //读取字符数
        int bStep = 0; //关键字读取步骤
        int ele = -1; //读取的字节元素
        boolean isNote = false; //是否为注释段
        int upChar = -1; //上一个字符
        while((ele=in.read())!=-1) {
        	readCount++;
            if(bStep==0) {
            	if(ele=='%') {
                	//判断快到末尾 %>
            		if(isNote) {
            			//注释段
            			if(upChar=='#') {
            				//判断结束时，上一个字符务必是#
            				bStep = 1;
            			}
            		}else {
            			bStep = 1;
            		}
                }else {
                	if(readCount==1 && ele=='#') {
                		//第一个字符是#，整段都是注释
                		isNote = true;
                	}
                    reBos.write(ele);
                }
            }else if(bStep==1) {
                if(ele=='>') {
                    break;
                }else {
                    bStep = 0;
                    reBos.write('%');
                    reBos.write(ele);
                }
            }
            upChar = ele;
        }
        if(enc==null || enc.length()<1) {
            return reBos.toString();
        }
        return reBos.toString(enc);
    }
    //#endregion
    
    //#region 【内部方法】 fixScript(out,script,req,resp,pageParaMap,fileIdMap,languageCode,outLevel,pathLevel) 处理脚本内容
    /**
     * 处理脚本内容
     * @param out          输出流
     * @param script       脚本
     * @param req          页面请求
     * @param resp         页面反馈
     * @param pageParaMap  页面参数代码（注意：该容器绝对不许为空）
     * @param fileIdMap    文件引用对照表
     * @param languageCode 语言代码
     * @param outLevel     是否输出内容
     * @param pathLevel    当前请求路径的相对路径（相对根路径）层级
     * @return             是否继续输出
     * @throws Exception   异常
     * 2014年11月26日
     * @author 马宝刚
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
	protected boolean fixScript(
            OutputStream        out
            ,String             script
            ,IRequestManager    req
            ,IResponseManager   resp
            ,Map<String,String> pageParaMap
            ,Map<String,String> fileIdMap
            ,String             languageCode
            ,List<Boolean>      outLevel
            ,int                pathLevel) throws Exception {
        if(script==null) {
            if(outLevel.size()<1) {
                return true;
            }
            return outLevel.get(outLevel.size()-1);
        }
        script = BaseUtil.trim(script," ","\t","\r","\n");
        if(script.startsWith("#")) {
            //注释
            if(outLevel.size()<1) {
                return true;
            }
            return outLevel.get(outLevel.size()-1);
        }
        int point; //分割点
        if("ift".equals(script) || "iff".equals(script)) {
            if(outLevel.size()<1) {
                return true;
            }
            outLevel.remove(outLevel.size()-1);
            if(outLevel.size()<1) {
                return true;
            }
            return outLevel.get(outLevel.size()-1);
        }
        if(script.startsWith("ift:")) {
            if(outLevel.size()>0 && !outLevel.get(outLevel.size()-1)) {
                //嵌套模式中，父节点为假，说明其内部都不显示，故都返回假
                outLevel.add(false);
                return false;
            }
            //条件判断（条件成立时显示其内容）
            script = script.substring(4);
            point = script.indexOf(":"); //来源分隔符
            if(point<0) {
                outLevel.add(true);
                return true; //语法错误
            }
            //来源
            String source =  BaseUtil.trim(script.substring(0,point)," ","\t","\r","\n");
            script = script.substring(point+1);
            
            point = script.indexOf(":"); //来源分隔符
            if(point<0) {
                outLevel.add(true);
                return true; //语法错误
            }
          //参数主键
            String key = BaseUtil.trim(script.substring(0,point)," ","\t","\r","\n");
            script = BaseUtil.trim(script.substring(point+1)," ","\t","\"","\'","\r","\n");
            //获取到的值         [NULL] 为空值常量
            String value = getParaValue(source,key,req,resp,pageParaMap);
            if(value.equals(script) || ("[NULL]".equals(script) && value.length()<1)) {
                outLevel.add(true);
                return true;
            }
            outLevel.add(false);
            return false;
        }else if(script.startsWith("iff:")) {
            if(outLevel.size()>0 && !outLevel.get(outLevel.size()-1)) {
                //嵌套模式中，父节点为假，说明其内部都不显示，故都返回假
                outLevel.add(false);
                return false;
            }
            //条件判断（条件不成立时显示其内容）
            script = script.substring(4);
            point = script.indexOf(":"); //来源分隔符
            if(point<0) {
                outLevel.add(true);
                return true; //语法错误
            }
            //来源
            String source =  BaseUtil.trim(script.substring(0,point)," ","\t","\r","\n");
            script = script.substring(point+1);
            
            point = script.indexOf(":"); //来源分隔符
            if(point<0) {
                outLevel.add(true);
                return true; //语法错误
            }
          //参数主键
            String key = BaseUtil.trim(script.substring(0,point)," ","\t","\r","\n");
            script = BaseUtil.trim(script.substring(point+1)," ","\t","\"","\'","\r","\n");
            //获取到的值    [NULL] 为空值常量
            String value = getParaValue(source,key,req,resp,pageParaMap);
            if(value.equals(script) || ("[NULL]".equals(script) && value.length()<1)) {
                outLevel.add(false);
                return false;
            }
            outLevel.add(true);
            return true;
        }
        if(outLevel.size()>0 && !outLevel.get(outLevel.size()-1)) {
            //如果不显示其内容，也就没必要解析脚本了，直接不显示
            return false;
        }
        if(script.startsWith("[")) {
          //国际化支持
            String outValue = ""; //输出语言内容
            //语言段
            String subLanguage;
            String code; //语言代码
            while(true) {
                point = script.indexOf("]");
                if(point<0) {
                    //非法内容，有了[符号居然没有]符号
                    break;
                }
                //语言段
                subLanguage = script.substring(1,point);
                point = subLanguage.indexOf(":");
                if(point<0) {
                    //输出默认语言内容
                    outValue = subLanguage;
                }else {
                    code = BaseUtil.trim(subLanguage.substring(0,point)," ","\t","\r","\n");
                    if(code.equalsIgnoreCase(languageCode)) {
                        if(encoding==null) {
                            out.write(subLanguage.substring(point+1).getBytes());
                        }else {
                            out.write(subLanguage.substring(point+1).getBytes(encoding));
                        }
                        return true;
                    }
                }
                point = script.indexOf("]");
                if(point<0) {
                    break;
                }
                script = BaseUtil.trim(script.substring(point+1)," ","\t","\r","\n");
            }
            if(outValue.length()>0) {
                out.write(outValue.getBytes());
            }
        }else if(script.startsWith("=")) {
            //输出变量值
            String key = script.substring(1);
            if("ctx".equalsIgnoreCase(key)) {
                //输出虚拟路径
                out.write(req.getContextPath().getBytes());
            }else if(key.startsWith("up:")) {
            	//相对路径即： <%=up:1%>  即返回当前请求的相对根路径的相对路径，其中1是保留一级路径
            	//比如当前请求 /path1/path2/path3/index.html  调用up功能返回  ../../.. 并且保留一级，最终为 ../..
            	
            	//这么做可以避免将 /path1 路径名写死在页面中
            	
            	//实际上的相对路径为： /path1
            	//这样避免写死 path1 的名字
            	key = key.substring(3);
        		//返回 ../.. 等
        		out.write(getSubPath(pathLevel-SInteger.valueOf(key)).getBytes());
            }else if("location".equalsIgnoreCase(key)) {
                //输出当前页面的url
                //相对路径 
                String servletPath = req.getContextPath()+req.getServletPath();
                //提交参数
                String queryString = SString.valueOf(req.getQueryString());
                if(queryString.length()>0) {
                    servletPath += "?"+queryString;
                }
                out.write(servletPath.getBytes());
            }else if("referer".equalsIgnoreCase(key)) {
                //上一个页面的url
            	String referer = SString.valueOf(req.getHeader("referer"));
            	if(referer.length()<1) {
            		referer = req.getCookieValue("referer");
            	}
                out.write(referer.getBytes());
            }else if("location_enc".equalsIgnoreCase(key)) {
                //输出当前页面的url
                //相对路径 
                String servletPath = req.getContextPath()+req.getServletPath();
                //提交参数
                String queryString = SString.valueOf(req.getQueryString());
                if(queryString.length()>0) {
                    servletPath += "?"+queryString;
                }
                out.write(StringUtil.getURLEncoding(servletPath).getBytes());
            }else {
                point = key.indexOf(":"); //来源分隔符
                String source = null; //参数值来源
                if(point<0) {
                    //默认为request方式
                    source = "r";
                }else {
                    //来源
                    source = BaseUtil.trim(key.substring(0,point)," ","\t","\r","\n");
                    key = BaseUtil.trim(key.substring(point+1)," ","\t","\"","\'","\r","\n");
                }
                String value = getParaValue(source,key,req,resp,pageParaMap); //参数值
                if(value.length()>0) {
                    if(encoding==null) {
                        out.write(value.getBytes());
                    }else {
                        out.write(value.getBytes(encoding));
                    }
                }
            }
        }else if(script.startsWith("redir:")) {
            //页面重定向
            script = script.substring(6);
            //处理url中的变量值
            script = BaseUtil.swapString(script,"@ctx@",req.getContextPath());
            //相对路径 
            String servletPath = req.getContextPath()+req.getServletPath();
            //提交参数
            String queryString = SString.valueOf(req.getQueryString());
            if(queryString.length()>0) {
                servletPath += "?"+queryString;
            }
            //当前url
            script = BaseUtil.swapString(script,"@location@",servletPath);
            //转义后的当前url
            script = BaseUtil.swapString(script,"@location_enc@",StringUtil.getURLEncoding(servletPath));
            //执行重定向
            resp.sendRedirect(script);
            //抛出已经重定向的异常
            throw new RedirectException();
        }else if(script.startsWith("file:")) {
            //加载文件
            //<%file:/inc/head.html%>
            script = script.substring(5);
            
            if(script.startsWith("up:")) {
            	//为相对根路径，并保留根路径往后的某一级子路径
            	script = script.substring(3);
            	
            	int subLevel = pathLevel; //相对根路径层级
            	point = script.indexOf(":");
            	if(point>0) {
            		subLevel = subLevel-SInteger.valueOf(script.substring(0,point));
            		script = script.substring(point+1);
            	}
            	script = getSubPath(subLevel)+script;
            }else if(script.startsWith("f:")) {
            	//路径的前半部分从配置文件中获取
            	script = script.substring(2);
            	
            	String propKey = ""; //参数主键
            	point = script.indexOf(":");
            	if(point>0) {
            		propKey = script.substring(0,point);
            		script = script.substring(point+1);
            	}
            	if(propKey.length()>0) {
            		script = p(propKey)+script;
            	}
            }
            point = script.indexOf("?"); //分割参数信息
            //子文件参数容器
            Map<String,String> cPageParaMap = new HashMap<String,String>();
            cPageParaMap.putAll(pageParaMap);
            if(point>0) {
                //放入子文件参数
                cPageParaMap.putAll(StringUtil.fixQueryString(script.substring(point+1)));
                script = script.substring(0,point);
            }
            //加载子文件
            byte[] resBytes = getFileBytes(script,req,resp,cPageParaMap);
            if(resBytes!=null) {
                out.write(resBytes);
            }
        }else if(script.startsWith("call:")) {
            //调用内部外部动作路径
            //<%call:http://www.baidu.com/search?para1=value1%>  外部
            //<%call:/action.ha?para1=value1%>  内部调用动作，无需写域名，虚拟路径等等，但是需要写动作路径扩展名
            String sEnc = null; //发送编码
            String rEnc = null; //返回编码
            script = script.substring(5);
            if(script.startsWith("[")) {
                //如果脚本的格式为<%call:[UTF-8|GBK]http://www......%>
                //中括号中用竖线分割了两个编码，头一个是发送编码，第二个是接收编码
                point = script.indexOf("]");
                if(point>0) {
                    //编码信息
                    String enc = script.substring(1,point);
                    script = script.substring(point+1);
                    
                    point = enc.indexOf("|");
                    if(point>0) {
                        sEnc = enc.substring(0,point);
                        rEnc = enc.substring(point+1);
                    }
                }
            }
            if(sEnc==null || sEnc.length()<1){
                sEnc = req.getCharacterEncoding();
            }
            if(script.indexOf("://")<0) {
                //本地方法调用
                if(!script.startsWith("/")) {
                    script = "/"+script;
                }
				//获取过滤器管理类实例
				bf = getBean(BaseFilter.class);
				//构造新的页面请求
				IRequestManager  fReq  = req.newInstance(); 
				//构造新的页面反馈
				IResponseManager fResp = resp.newInstance();
				//网站根路径，相对于WEB-INF上一级文件夹
				String webBasePath = filesUtil.getAllFilePath("..");
				//设置url信息
				fReq.setUrlInfo(webBasePath,req.getContextPath(),script,null);
				
				fReq.setParameter("_caller_uri",req.getRequestURI());
				fReq.setParameter("_caller_url",str(req.getRequestURL()));
				
				if(pageParaMap!=null) {
					//获取提交参数容器
					List<String> keyList = BaseUtil.getMapKeyList(pageParaMap);
					//页面请求容器
					Map<String,String[]> reqMap = req.getParameterMap();
					for(String key:keyList) {
						if(reqMap.containsKey(key)) {
							continue;
						}
						req.setParameter(key,pageParaMap.get(key));
					}
				}
				//调用内部动作
				bf.doFilter(fReq, fResp);
				ByteArrayOutputStream bos = fResp.getData();
				if(bos.size()>0) {
					out.write(bos.toByteArray());
				}
            }else {
                //全路径
                //http://www.baidu.com
            	
                //提交参数容器
                Map paraMap = req.getParameterMap();
                
                paraMap.put("_caller_uri",req.getRequestURI());
                paraMap.put("_caller_url",str(req.getRequestURL()));
                
                //主意：这么做很危险，如果调用外部的动作，把当前页面的会话主键都发给人家了，这不是作死嘛
                //paraMap.put("_caller_session_id",req.getSession().getId());
                
                if(rEnc==null || rEnc.length()<1) {
                    out.write(HttpCall.callReturnBytes(script,StringUtil.getParaStringFromMap(paraMap).getBytes(sEnc),getHeaderMap(req)));
                }else {
                    out.write(HttpCall.call(script,StringUtil.getParaStringFromMap(paraMap),sEnc,rEnc,0,getHeaderMap(req)).getBytes());
                }
            }
        }else if(script.startsWith("set:")) {
            //设置页面参数
            if(pageParaMap!=null) {
                pageParaMap.putAll(StringUtil.fixQueryString(script.substring(4)));
            }
        }else if(script.startsWith("file[")) {
            //带主键的文件引用方式
            //<%file[文件位置主键]:文件路径%>

            //加载文件
            String subScript = script.substring(5);
            
            point = subScript.indexOf("]");
            if(point<0) {
                log.error("***********Error ByteFilter Script:["+script+"] The Format Must Be <%file[file_id]:File_Path%>",null);
                return true;
            }
            //文件主键
            String fileId = subScript.substring(0,point);
            String filePath = null; //文件路径
            if(fileIdMap!=null && fileIdMap.containsKey(fileId)) {
                Object fileObj = fileIdMap.get(fileId); //获取到的值可能是路径，也可能是读入流
                if(fileObj instanceof InputStream) {
                    out.write(fixContent((InputStream)fileObj,req,resp,pageParaMap));
                    return true;
                }
                filePath = SString.valueOf(fileObj);
            }
            if(filePath==null || filePath.length()<1) {
                point = subScript.indexOf(":");
                if(point<0) {
                    //log.error("***********Error ByteFilter Script:["+script+"] The Format Must Be <%file[file_id]:File_Path%>",null);
                    return true;
                }
                filePath = subScript.substring(point+1);
            }
            point = filePath.indexOf("?"); //分割参数信息
            //子文件参数容器
            Map<String,String> cPageParaMap = new HashMap<String,String>();
            cPageParaMap.putAll(cPageParaMap);
            if(point>0) {
                //放入子文件参数
                cPageParaMap.putAll(StringUtil.fixQueryString(filePath.substring(point+1)));
                filePath = filePath.substring(0,point);
            }
            //加载子文件
            byte[] resBytes = getFileBytes(script,req,resp,cPageParaMap);
            if(resBytes!=null) {
                out.write(resBytes);
            }
        }
        return true;
    }
    //#endregion
    
    //#region 【内部方法】 getHeaderMap(req) 输出头包
    /**
     * 输出头包
     * @param req 页面请求
     * @return 头包
     * 2015年2月3日
     * @author 马宝刚
     */
    private Map<String,String> getHeaderMap(IRequest req){
        //构建返回值
        Map<String,String> reMap = new HashMap<String,String>();
        //头迭代
        Enumeration<String> names = req.getHeaderNames();
        String name; //头主键
        while(names.hasMoreElements()) {
            name = names.nextElement();
            reMap.put(name,req.getHeader(name));
        }
        return reMap;
    }
    //#endregion
    
    //#region 【内部方法】getParaValue(source,key,req,resp,pageParaMap) 根据指定源获取对应的参数值
    /**
     * 根据指定源获取对应的参数值
     * @param source 来源
     * 
     *  request 或 r     页面请求参数 request.getParameter
     *  session 或 s    会话属性值  session.getAttribute
     *  page 或 p       页面参数
     *  parameter 或 f  配置文件参数值
     * 
     * @param key         参数主键
     * @param req         页面请求
     * @param resp        页面反馈
     * @param pageParaMap 页面参数容器
     * @return 参数值
     * 2014年11月26日
     * @author 马宝刚
     */
    private String getParaValue(String source,String key,IRequest req,IResponse resp,Map<String,String> pageParaMap) {
        if(key==null || key.length()<1) {
            return "";
        }
        if(source==null || source.length()<1) {
            return "";
        }
        if("request".equalsIgnoreCase(source) || "r".equalsIgnoreCase(source)) {
            //Request.getParameter
            String value = SString.valueOf(req.getParameter(key));
            if(value.length()<1) {
                value = SString.valueOf(StringUtil.fixQueryString(req.getQueryString()).get(key));
            }
            return value;
        }else if("session".equalsIgnoreCase(source) || "s".equalsIgnoreCase(source)) {
            //Session.getAttribute
        	try {
        		return SString.valueOf(req.iGetSession().getAttribute(key));
        	}catch(Exception e) {
        		e.printStackTrace();
        		throw new RuntimeException(e);
        	}
        	
        }else if("page".equalsIgnoreCase(source) || "p".equalsIgnoreCase(source)) {
            //页面配置参数
            if(pageParaMap==null) {
                return "";
            }
            return SString.valueOf(pageParaMap.get(key));
        }else if("parameter".equalsIgnoreCase(source) || "f".equalsIgnoreCase(source)) {
            //配置文件参数
            return SString.valueOf(prop.getParameter(key));
        }
        return "";
    }
    //#endregion
    
    //#region 【内部方法】getFileBytes(path,req,resp,pageParaMap) 获取引用文件内容字节
    /**
     * 获取引用文件内容字节
     * @param path 引用文件路径（可以是相对路径）
     * @param req  页面请求
     * @param resp 页面反馈
     * @param pageParaMap 页面参数容器
     * @return 文件内容字节数组
     * @throws Exception 异常
     * 2014年10月24日
     * @author 马宝刚
     */
    private byte[] getFileBytes(String path,IRequestManager req,IResponseManager resp,Map<String,String> pageParaMap) throws Exception {
        if(path==null) {
            return new byte[0];
        }
        if(!path.startsWith("/")) {
        	path = SFilesUtil.getFilePath(req.getServletPath())+path;
        }
        //指定文件
        File file =new File(SFilesUtil.getAllFilePath(path,req.getWebBasePath()));
        if(file.exists()) {
            try {
                return fixContent(new FileInputStream(file),req,resp,pageParaMap);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        return new byte[] {};
    }
    //#endregion
    
    //#region 【内部方法】getSubBasePath(req) 通过当前请求的URL返回相对于根路径的子路径层级
    /**
     * 通过当前请求的URL返回相对于根路径的子路径层级
     * 
     * 比如：当前请求  /path1/path2/index.html
     *       返回相对路径层级 2
     * 
     * @param req     动作请求
     * @return        相对于根路径的子路径层级
     * 2017年9月5日
     * @author MBG
     */
    private int getSubBasePath(IRequestManager req) {
    	//构建返回值
    	int label = 0;
    	//转换为字符数组
    	char[] chars = req.getServletPath().toCharArray();
    	//注意：起始索引为1
    	for(int i=1;i<chars.length;i++) {
    		if(chars[i]=='/') {
    			label++;
    		}
    	}
    	return label;
    }
    //#endregion
    
    //#region 【内部方法】 getSubPath(subLevel) 输出指定层级的上一级相对路径
    /**
     * 输出指定层级的上一级相对路径
     * @param subLevel 层级
     * @return 指定层级的上一级相对路径，即: /../..
     * 2017年9月5日
     * @author MBG
     */
    private String getSubPath(int subLevel) {
    	//构建返回值
    	StringBuffer reSbf = new StringBuffer();
    	if(subLevel>0) {
    		for(int i=0;i<subLevel;i++) {
    			if(i==0) {
    				reSbf.append("..");
    			}else {
    				reSbf.append("/..");
    			}
    		}
    	}
    	return reSbf.toString();
    }
    //#endregion
    
    //#region 【内部方法】fixContent(in,out,req,resp,pageParaMap,fileIdMap) 处理读入流内容
    /**
     * 处理读入流内容
     * @param in          读入流
     * @param out         输出流
     * @param req         页面请求
     * @param resp        页面反馈
     * @param pageParaMap 页面参数容器
     * @param fileMap     文件主键路径对照容器
     * @throws Exception  异常
     * 2014年12月9日
     * @author 马宝刚
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void fixContent(
            InputStream            in
            ,ByteArrayOutputStream out
            ,IRequestManager       req
            ,IResponseManager      resp
            ,Map<String,String>    pageParaMap
            ,Map                   fileIdMap) throws Exception {

        //先从会话中获取语言代码
        String languageCode = 
                SString.valueOf(req.iGetSession().getAttribute("locale"));
        if(languageCode.length()<1) {
            languageCode = req.getLocale().toString().toUpperCase();
        }
        if(pageParaMap==null) {
            pageParaMap = new HashMap<String,String>();
        }
        //编码格式
        String enc = req.getCharacterEncoding();
        if(enc==null || enc.length()<1) {
        	enc = "UTF-8";
        }
        //转译后的代码
        String oCode = getLanguageCode(languageCode);
        if(oCode.length()>0) {
            languageCode = oCode;
        }
        //当前请求路径的相对路径（相对根路径）层级
        int pathLevel = getSubBasePath(req);
        int bStep = 0; //读取关键字步骤
        boolean outContent = true; //是否输出内容
        List<Boolean> outLevel = new ArrayList<Boolean>(); //用于嵌套输出 判断
        
        try {
            int ele = -1; //读取的字节元素
            /*
             * 注意：动态内容需要用 <%  %>包含进来
             * 更加注意：如果有<% 必须要有 %>作为结束
             */
            while((ele=in.read())!=-1) {
                if(bStep==0) {
                  //获取处理信息节点关键字的第一个字节
                    if(ele=='<') {
                        bStep = 1;
                    }else {
                        if(outContent) {
                            out.write(ele);
                        }
                    }
                }else if(bStep>0) {
                    //已经找到<字符
                	if(bStep==1) {
                		if(ele=='!') {
                			// <!
                    		bStep = 2;
                    		continue;
                		}
                		//#region 处理 <% %> 动态标签
                    	// 看看第二个字符是不是 %
                        bStep = 0;
                        if(ele=='%') {
                            //确定是动态内容 绝不忽悠
                            outContent = fixScript(out,getScript(in,req.getCharacterEncoding()),req,resp,pageParaMap,fileIdMap,languageCode,outLevel,pathLevel); 
                        }else if(ele=='?') {
                            //页面头部动态脚本处理
                            
                            //脚本对象
                            Json headJson = fixHeaderScript(in,enc);
                            if("model".equals(headJson.getValue("type"))) {
                              //<?{"type":"model","path":"框架模板路径","id":"当前页面内容需要放到框架模板中指定引用文件位置的id","other":{"框架模板页面引用文件主键":"文件路径","top":"/inc/top.html"}}?>
                                
                                String pagePath = headJson.getValue("path"); //框架页面路径
                                String thisId = headJson.getValue("id"); //当前页面在框架页面中对应的id
                                
                                //文件路径对照容器(不能用fileIdMap，真的不行，因为。。。此处省略5000字)
                                Map pathMap = headJson.getChildJson("other").getValueMap();
                                pathMap.put(thisId,in); //将当前读入流也放入容器中
                                
                                if(pagePath==null || pagePath.length()<1) {
                                    throw new MsgException(this,"The Script <?{\"type\":\"model\" ....   path parameter is empty");
                                }
                                //子页面参数容器
                                Map<String,String> cPageParaMap = new HashMap<String,String>();
                                cPageParaMap.putAll(pageParaMap);
                                int point = pagePath.indexOf("?"); //参数分隔符
                                if(point>0) {
                                	cPageParaMap.putAll(StringUtil.fixQueryString(pagePath.substring(point+1)));
                                    pagePath = pagePath.substring(0,point);
                                }
                                if(!pagePath.startsWith("/")) {
                                	pagePath = SFilesUtil.getFilePath(req.getServletPath())+pagePath;
                                }
                                //相对于当前
                                pagePath = SFilesUtil.getAllFilePath(pagePath,req.getWebBasePath());
                                
                                //构建目标文件对象
                                File objFile = new File(pagePath);
                                if(!objFile.exists()) {
                                	resp.sendError(404,req);
                                	return;
                                }
                                fixContent(new FileInputStream(objFile),out,req,resp,cPageParaMap,pathMap);
                                break;
                            }else if(headJson.containsKey("http_header")) {
                            	//设置头值
                            	Json headerJson = headJson.getChildJson("http_header");
                            	if(headerJson!=null) {
                            		//获取需要设置的头信息值容器
                            		Map<String,Object> valueMap = headerJson.getValueMap();
                            		if(valueMap!=null) {
                            			//主键名序列
                            			List<String> keyList = BaseUtil.getMapKeyList(valueMap);
                            			for(String key:keyList) {
                            				resp.setHeader(key,str(valueMap.get(key)));
                            			}
                            		}
                            	}
                            }else if(headJson.containsKey("session_not_val_stop")) {
                            	// 如果会话中不存在某个值，则终止输出后续代码
                            	Object sVal = req.iGetSession().getAttribute(headJson.getValue("session_not_val_stop"));
                            	if(sVal==null || "".equals(sVal)) {
                            		if(headJson.containsKey("redir")) {
                            			resp.sendRedirect(headJson.getValue("redir"));
                            		}
                            		return;
                            	}
                            }
                        }else {
                            //头一个<不是关键字
                            if(outContent) {
                                out.write('<');
                                out.write(ele);
                            }
                        }
                        //#endregion
                        continue;
                	}else if(bStep==2 && ele=='-') {
                		// <!-
                		bStep = 3;
                		continue;
                	}else if(bStep==3 && ele=='-') {
                		// <!-- 
                		bStep = 4;
                		continue;
                	}else if(bStep==4 && ele=='#') {
                		// <!--#include 命中
                		bStep = 0;
                		// 解析SSI节点
                		parseSsi(out,getSSiContent(in),req,resp,pageParaMap);
                	}else {
                		if(outContent) {
                			out.write('<');
                			if(bStep==2) {
                				out.write('!');
                			}else if(bStep==3) {
                				out.write('!');
                				out.write('-');
                			}else if(bStep==4) {
                				out.write('!');
                				out.write('-');
                				out.write('-');
                			}
                			out.write(ele);
                		}
                		bStep = 0;
                	}
                }
            }
        }finally {
            try {
                in.close();
            }catch(Exception e2) {}
        }
    }
    //#endregion
    
    //#region fileContent(file,req,resp,pageParaMap) 处理文件内容
    /**
     * 处理文件内容
     * 刘虻
     * 2019-08-20 10:39
     * @param file         指定文件
     * @param req          页面请求
     * @param resp         页面反馈
     * @[param pageParaMap 页面参数容器
     * @return             处理后的文件内容
     * @throws Exception 异常
     */
    public String fileContent(
             File               file
            ,IRequestManager    req
            ,IResponseManager   resp
            ,Map<String,String> pageParaMap) throws Exception {
        //构建返回值
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        //处理内容
        fixContent(new FileInputStream(file),out,req,resp,pageParaMap,null);
        return out.toString();
    }
    //#endregion
    
    //#region fileContent(subPath,req,resp,pageParaMap) 处理文件内容（包含框架包内部文件）
    /**
     * 处理文件内容（包含框架包内部文件）
     * @param subPath      指定文件相对路径（相对网站根路径，如果为/adm/开头的，为框架包内部文件路径）
     * @param req          页面请求
     * @param resp         页面反馈
     * @param pageParaMap  页面参数容器
     * @return             处理后的文件内容
     * @throws Exception   异常
     * 2019年9月24日
     * @author MBG
     */
    public String fileContent(
    		 String             subPath
    		,IRequest           req
    		,IResponse          resp
    		,Map<String,String> pageParaMap) throws Exception {
    	if(subPath==null || subPath.length()<1) {
    		return "";
    	}
        //构建返回值
        ByteArrayOutputStream out = new ByteArrayOutputStream();
    	//目标文件
    	File file = new File(path(subPath));
    	if(file.exists()) {
            //处理内容
            fixContent(new FileInputStream(file),out,(IRequestManager)req,(IResponseManager)resp,pageParaMap,null);
    	}
        return out.toString();
    }
    //#endregion
    
    //#region fixContent(in,req,resp,pageParaMap) 处理文件内容
    /**
     * 处理文件内容
     * 刘虻
     * 2013-4-6 下午10:03:25
     * @param file 指定文件
     * @param req 页面请求
     * @param resp 页面反馈
     * @[param pageParaMap 页面参数容器
     * @return 处理后的文件内容字节数组
     * @throws Exception 异常
     */
    public byte[] fixContent(
             InputStream        in
            ,IRequestManager    req
            ,IResponseManager   resp
            ,Map<String,String> pageParaMap) throws Exception {
        //构建返回值
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        //处理内容
        fixContent(in,out,req,resp,pageParaMap,null);
        return out.toByteArray();
    }
    //#endregion
    
    //#region 【内部方法】fixHeaderScript(in,enc) 读取页面头部脚本
    /**
     * 读取页面头部脚本
     * @param in 读入流
     * @param enc 编码格式
     * @return 脚本json对象
     * @throws Exception 读取发生异常
     * 2014年12月9日
     * @author 马宝刚
     */
    private Json fixHeaderScript(InputStream in,String enc) throws Exception {
        int ele = -1; //读取的字节元素
        int bStep = 0; //读取关键字步骤
        boolean readOk = false; //是否读取完毕
        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        /*
         * 注意：动态内容需要用 <?  ?>包含进来
         * 更加注意：如果有<? 必须要有 ?>作为结束
         */
        while((ele=in.read())!=-1) {
            if(bStep==0) {
                if(ele=='?') {
                    bStep = 1;
                }else {
                    reBos.write(ele);
                }
            }else {
                if(ele=='>') {
                    //结束符
                    readOk = true;
                    break;
                }else {
                    reBos.write('?');
                    reBos.write(ele);
                    bStep = 0;
                }
            }
        }
        if(readOk) {
            return new Json(reBos.toString(enc));
        }
        throw new MsgException(this,"The Head Script Not Close  <?   ?>");
    }
    //#endregion
    
    //#region serveFile(req,res,file) 输出指定文件内容到页面中
    /**
     * 输出指定文件内容到页面中
     * 刘虻
     * 2012-7-27 上午9:09:36
     * @param req                       页面请求
     * @param res                       页面反馈
     * @param file                      文件对象
     * @return                            返回是否终止继续处理 true终止继续处理
     * @throws IOException      异常
     */
    public void serveFile(
             IRequest  req
            ,IResponse res
            ,File      file) throws Exception {
        if (!file.canRead()) {
            res.sendError(IResponse.SC_FORBIDDEN,req);
            return;
        } else {
            try {
                file.getCanonicalPath();
            } catch (Exception e) {
                res.sendError(IResponse.SC_FORBIDDEN,
                        "Forbidden, exception:" + e,req);
                return;
            }
        }
        res.setStatus(IResponse.SC_OK);
        //设置内容类型
        res.setContentType(getFilterConfig().getServletContext().getMimeType(file.getName()));
        
        res.setHeader("Cache-Control", "no-cache");
        res.setHeader("Pragma", "no-cache");
        res.setDateHeader("Expires", 0); 
        
        //获取处理后的文件内容
        byte[] contentBytes;
        try {
            contentBytes= fixContent(new FileInputStream(file),(IRequestManager)req,(IResponseManager)res,null); 
        }catch(RedirectException re) {
            throw new RedirectException();
        }catch(Exception e) {
            e.printStackTrace();
            res.sendError(IResponse.SC_INTERNAL_SERVER_ERROR,req);
            return;
        }
        if (contentBytes.length < Integer.MAX_VALUE) {
            res.setContentLength(contentBytes.length);
        }else {
            res.setHeader("Content-Length", Long.toString(contentBytes.length));
        }
        res.setDateHeader("Last-modified", file.lastModified());
        try {
            //输出内容
            res.iGetOutputStream().write(contentBytes);
        }catch(Exception e) {}
    }
    //#endregion
    
    //#region doBytesFilter(bytes,req,resp) 执行过滤
    /**
     * 执行过滤
     * @param bytes 过滤前的字节数组
     * @param req 页面请求对象 
     * @param resp 页面反馈对象
     * @return 过滤后的字节数组
     * @throws Exception 异常
     * 2014年6月30日
     * @author 马宝刚
     */
    @Override
    public byte[] doBytesFilter(byte[] bytes, IRequest req, IResponse resp) throws Exception {
        try {
            return fixContent(new ByteArrayInputStream(bytes),(IRequestManager)req,(IResponseManager)resp,null);
        }catch(RedirectException e) {
            //已经执行了重定向，无需再往下执行
            return null;
        }
    }
    //#endregion
    
    //#region doContentFilter(content,req,resp) 执行过滤
    /**
     * 执行过滤
     * @param content      待过滤的内容
     * @param req          页面请求对象
     * @param resp         页面反馈对象
     * @return             处理后的字节数组
     * @throws Exception   异常
     * 2017年11月8日
     * @author MBG
     */
    public byte[] doContentFilter(String content,IRequest req,IResponse resp) throws Exception {
        try {
            return fixContent(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)),(IRequestManager)req,(IResponseManager)resp,null);
        }catch(RedirectException e) {
            //已经执行了重定向，无需再往下执行
            return null;
        }
    }
    //#endregion
    
    //#region contentFilter(content,req,resp) 执行过滤
    /**
     * 执行过滤
     * @param content      待过滤的内容
     * @param req          页面请求对象
     * @param resp         页面反馈对象
     * @return             处理后的字符串
     * @throws Exception   异常
     * 2017年11月8日
     * @author MBG
     */
    public String contentFilter(String content,IRequest req,IResponse resp) throws Exception {
    	//获取返回值
    	byte[] res = doContentFilter(content,req,resp);
    	if(res==null) {
    		return "";
    	}
    	return new String(res, StandardCharsets.UTF_8);
    }
    //#endregion
    
    //#region doFileFilter(filePath,req,resp) 读取指定文档内容并做内容处理
    /**
     * 读取指定文档内容并做内容处理
     * @param filePath     文档全路径
     * @param req          页面请求
     * @param resp         页面反馈
     * @return             处理后的文件内容
     * @throws Exception   异常
     * 2017年9月11日
     * @author MBG
     */
    @Override
    public byte[] doFileFilter(String filePath, IRequest req, IResponse resp) throws Exception {
        try {
        	//目标文件
        	File file = new File(filePath);
        	if(!file.exists()) {
        		return null;
        	}
            return fixContent(new ByteArrayInputStream(FileCopyTools.copyToByteArray(file)),(IRequestManager)req,(IResponseManager)resp,null);
        }catch(RedirectException e) {
            //已经执行了重定向，无需再往下执行
            return null;
        }
    }
    //#endregion
    
    //#region getParameterMap() 获取参数对照容器
    /**
     * 获取参数对照容器
     * @return 参数对照容器
     * 2014年6月30日
     * @author 马宝刚
     */
    public Map<String,String> getParameterMap(){
        if(parameterMap==null) {
            parameterMap = new HashMap<String,String>();
        }
        return parameterMap;
    }
    //#endregion
    
    //#region getFilterActionExtName() 获取需要过滤的动作路径扩展名
    /**
     * 获取需要过滤的动作路径扩展名
     * 
     * 多个扩展名用逗号分割
     * 多个扩展名用半角逗号分割
     * 扩展名前不用加半角聚号（.)
     * 如果返回空，或者空字符串，说明需要过滤全部动作路径（不建议这么做）
     * 如果需要过滤无扩展名的动作，用半角减号（-）标记
     * 
     * 注意：htm和ha并不在这里注册，因为动作类会调用 doBytesFilter方法
     * 
     * @return
     * 2014年9月12日
     * @author 马宝刚
     */
    @Override
    public String getFilterActionExtName() {
        if(filterActionExtName==null) {
            //js,css 屏蔽掉 js 和css ，比较耗时
            filterActionExtName = "html";
        }
        return filterActionExtName;
    }
    //#endregion
    
    //#region init(bf,config) 执行初始化
	/**
	 * 执行初始化
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(BaseFilter bf, IFilterConfig config) throws Exception {
		this.bf       = bf;
		this.config   = config;
    	this.encoding = config.getInitParameter("encoding");
    	if(this.encoding==null || this.encoding.length()<1) {
    		this.encoding = "UTF-8";
    	}
    	if(boo(p("filter_parse_html_file"))) {
    		filterActionExtName = "html";
    	}else {
    		filterActionExtName = "htm";
    	}
	}
	//#endregion

	//#region destroy() 销毁当前过滤器
	/**
	 * 覆盖方法
	 */
	@Override
	public void destroy() {}
	//#endregion
}











